#include "indexado.h"

#define TAM_OCUPADO_CABECERA	8

Indexado::Indexado(std::string& nombre, unsigned int tam_bloque,unsigned int tam_b_indice):ArchivoDatos(nombre, tam_bloque, tam_b_indice){
	std::string aux = nombre +EXT_EELL;
	espacios_libres = new ArchEspaciosLibres(aux,TAM_EELL);
	indice_secundario = NULL; 
	std::string nombre_ind_primario = nombre + ".tree";
	indice_primario = new IndiceArbol(nombre_ind_primario,tam_b_indice,(TiposArchivo)INDICE_B);
}

Indexado::~Indexado(){
	//Si el archivo esta abierto, lo cerramos
	this->cerrar();
	//deleteo el contenido del archivo
	delete(espacios_libres);
	delete(indice_primario);
	if (indice_secundario!= NULL)
		delete indice_secundario;	
}
	
void Indexado::abrir() throw(ExcepAbriendoArch){
	(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Abriendo el archivo "+ this->get_nombre());
	//Si el archivo de datos esta abierto, arrojo excep
	if (this->fd != -1)
		throw ExcepAbriendoArch("Error - El archivo: " + this->get_nombre() + " ya estaba abierto.");
	else {
		std::string dest = "./DATA/" + this->get_nombre();
		this->fd = ManejadorArchivos::get_instance()->abrir_archivo(dest);
		if ( this->fd == -1)
			throw ExcepAbriendoArch (ManejadorErrores::get_msg_error(errno) + " Archivo: " + this->get_nombre());
		espacios_libres->abrir(); //arroja una excepcion en caso de no poder abrirlo
		indice_primario->abrir();
		if(this->indice_secundario != NULL)
			indice_secundario->abrir();
		espacios_libres->agregar_observador(Buffer::get_instance());
		this-> cant_bloques = get_cant_bloques_header();
	}	
}
				
void Indexado::crear(std::string formato) throw(ExcepCreandoArch, ExcepSintaxisParser, ExcepBloqueTam){
	(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Creando el archivo "+ this->get_nombre());
	if(this->get_tam_bloque() < TAM_OCUPADO_CABECERA)
		throw ExcepBloqueTam("Error - No se pudo crear el archivo. El tamaño del bloque es muy pequeno para la cabecera");
	else{
		try{
			Archivo::cargar_registro_base(formato);
			TipoStruct * reg = get_registro_base();
			if(reg->get_tamanio() == 0)
				throw ExcepSintaxisParser("Error - No se ingreso formato para el archivo");
			if((*reg)[0].get_codigo_tipo() != INT)
				throw ExcepSintaxisParser("Error - El campo 0 del archivo 'indexado' debe ser numerico. ");	
			
			std::string dest = "./DATA/" + this->get_nombre();
			this->fd = ManejadorArchivos::get_instance()->crear_archivo(dest);
			if ( this->fd == -1 )
				throw ExcepCreandoArch (ManejadorErrores::get_msg_error(errno) + " Archivo: " + this->get_nombre());
			espacios_libres->crear(); //arroja una excepcion en caso de no poder crearlo.
			espacios_libres->agregar_observador(Buffer::get_instance());
			indice_primario->crear();
			//if(this->indice_secundario != NULL)
				//indice_secundario->crear(get_nombre()+".indSec");
		
			//Crea el header, y le guarda el formato
			dest = this->get_nombre() + ".head";
			Header head(dest);
			head.crear();
			head.guardar(formato, this);
			head.cerrar();
			
			//Creo el bloque 0 de header y lo guardo
			Bloque* bloque_header = new Bloque(0, this->get_tam_bloque());
			Buffer::get_instance()->guardar_bloque(*this, bloque_header);
			delete (bloque_header);
			//Asigno que la cant de bloques es 1
			this->set_cant_bloques_header(1);
			this->cant_bloques = 1;
			
			
		}
		catch(ExcepSintaxisParser &e){
			throw e; //para hacerlo explicito
		}
	}	
}
		
void Indexado::cerrar() {
	(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Cerrando el archivo "+ this->get_nombre());
	if(fd != -1){
		set_cant_bloques_header(this->cant_bloques);
		this->set_cambio();
		this->avisar_observadores(NULL);
		ManejadorArchivos::get_instance()->cerrar_archivo(this->fd);
		espacios_libres->cerrar();
		indice_primario->cerrar();
		if (indice_secundario !=NULL)
			indice_secundario->cerrar();
		this->fd = -1;
	}
}
		
void Indexado::ingresar(TipoStruct * reg) throw(ExcepArchCerrado,ExcepRegExistente, ExcepBloqueTam){
	Bloque* bloque_actual = NULL;
	(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se ingresa un nuevo registro." );
	if (this->fd == -1)
		throw ExcepArchCerrado("Error - El archivo : "+ this-> get_nombre() +" esta cerrado. ");
	if (es_unico(reg)){
		unsigned int tam = reg->get_tamanio_serializado();
		//Verifico que el registro tenga menor tamanio que el bloque
		if( tam <= this->get_tam_bloque() - TAM_OCUPADO_CABECERA){ 
			//consulto en el archivo de espacios libres donde me conviene ubicarlo
			int num_bloque = espacios_libres->consultar_espacio_libre(tam);		
			if (num_bloque != -1){
				(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se encontro un lugar donde ingresar el registro." );
				num_bloque++; //Porque yo el 0 no lo meto en el de espacios libres
				//Obtengo el reg desde el buffer
				bloque_actual = Buffer::get_instance()->get_bloque(*this, num_bloque);
				//Pongo el registro en el bloque
				poner_reg_en_bloque(bloque_actual, reg);
			} 
			else{
				(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> No hay lugar. Se crea un nuevo bloque." );
				//Creo un bloque nuevo vacio
				bloque_actual = new Bloque(cant_bloques, this->get_tam_bloque());
				//this->set_cant_regs_bloque(bloque_actual, 0);
				//agrego una entrada para un nuevo bloque en el arch de espacios libres
				this->espacios_libres->append(bloque_actual->get_espacio_libre());
				//Pongo el registro en el bloque
				poner_reg_en_bloque(bloque_actual, reg);
				cant_bloques++; //Incremento la cant de bloques del arch
				num_bloque = bloque_actual->get_num_bloque();
			}
			(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Actualizo indice primario." );
			//actualizo el indice primario y de existir el secundario tbn
			indice_primario->ingresar(&(*reg)[0],num_bloque);
			if (indice_secundario != NULL)
				indice_secundario->ingresar(reg);
			delete (bloque_actual);
		}
		else{
			throw ExcepBloqueTam("Error - El dato a ingresar es mas grande que el tamano del bloque de " + this->get_nombre() + ". ");
		}	
	}
	else
		throw ExcepRegExistente("Error - El registro que intenta ingresar ya existe en el archivo. ");
}
		
void Indexado::quitar(std::vector<ParesDatos*> &elementos, std::vector<TipoStruct*> &resultado) throw(ExcepArchCerrado){
	(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se quita un registro." );
	if (this-> fd != -1){
		if( (elementos.size() != 1) || (elementos[0]->get_numero_campo() != 0) ){
			if(!quitar_indice_secundario(elementos,resultado)) {
				(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se quita recorriendo secuencialmente el archivo." );
				quitar_secuencial(elementos,resultado);
			} else
				(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se quita utilizando el indice secundario." );
		}
		else{
			(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se quita buscandolo por campo clave en indice primario." );
			TipoDato* clave = elementos[0]->get_valor();
			//obtengo a traves del indice el numero de bloque donde buscar el reg asociado a la clave
			int num_bloque = indice_primario->consultar(clave);
			if(num_bloque != Indice::VACIO){
				Bloque*	bloque_actual = Buffer::get_instance()->get_bloque(*this,num_bloque);
				TipoStruct* reg = (TipoStruct *)get_registro_base()->clonar();
				unsigned int pos_res, size_res;
				bool clave_encontrada;
				//lo busco dentro del bloque, se que es unico por que es consulta por campo 0			
				clave_encontrada = bloque_actual->buscar_por_clave_desde(0, elementos[0]->get_valor(),reg,pos_res, size_res);
				if (clave_encontrada){
					sacar_reg_bloque(bloque_actual,NULL, pos_res,size_res);
					resultado.push_back(reg);
					(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se quita del indice primario." );
					//actualizo ambos indices
					indice_primario->quitar(&((*reg)[0]));	
					if (indice_secundario!= NULL)
						indice_secundario->quitar(reg);
				}else
					delete (reg);			
				delete(bloque_actual);
			}
		}
	}else
		throw ExcepArchCerrado("Error - El archivo : "+ this-> get_nombre() +" esta cerrado. ");					 	
}

void Indexado::consultar(std::vector<ParesDatos*> &consultas, std::vector<TipoStruct*> &resultado)
												 throw(ExcepArchCerrado,ExcepLeerBloque){
	(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se realiza una consulta." );
	if (this-> fd != -1){
		if( (consultas.size() != 1) || (consultas[0]->get_numero_campo() != 0) ){
			if(!consultar_indice_secundario(consultas,resultado)) {
				(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se consulta recorriendo secuencialmente el archivo." );
				consultar_secuencial(consultas,resultado);
			} else
				(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se consulta utilizando el indice secundario." );		
		}
		else{
			(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se consulta por campo clave accediendo a indice primario." );
			TipoDato* clave = consultas[0]->get_valor();
			//obtengo a traves del indice el numero de bloque donde buscar el reg asociado a la clave
			int num_bloque = indice_primario->consultar(clave);
			if( num_bloque != Indice::VACIO ){
				Bloque*	bloque_actual = Buffer::get_instance()->get_bloque(*this,num_bloque);
				TipoStruct* reg = (TipoStruct*)get_registro_base()->clonar();
				unsigned int pos_res, size_res;
				bool clave_encontrada;
				//lo busco dentro del bloque, se que es unico por que es consulta por campo 0			
				clave_encontrada = bloque_actual->buscar_por_clave_desde(0,clave,reg, pos_res, size_res);
				if (clave_encontrada)
					resultado.push_back(reg);
				else
					delete (reg);			
				delete(bloque_actual);
			}
		}
	}else
		throw ExcepArchCerrado("Error - El archivo : "+ this-> get_nombre() +" esta cerrado. ");
}

void Indexado::actualizar(TipoStruct * reg) throw(ExcepArchCerrado,ExcepRegInexistente, ExcepBloqueTam){
	(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se actualiza un registro." );
	if (this-> fd != -1){
		(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Se accede por campo clave mediante indice primario." );
		//accedo al bloque del registro por el indice primario, por campo 0 
		TipoDato* clave = &((*reg)[0]);
		int num_bloque = indice_primario->consultar(clave);
		if (num_bloque!= Indice::VACIO ){ //quiere decir que el registro asociado a la clave existe dentro de un bloque
			Bloque*	bloque_actual = NULL;
			TipoStruct* reg_b;
			try{
				bloque_actual = Buffer::get_instance()->get_bloque(*this,num_bloque);
				reg_b = (TipoStruct*)get_registro_base()->clonar();
				unsigned int pos, size;
				bool encontro;
				//lo busco dentro del bloque, se que es unico por que es consulta por campo 0			
				encontro = bloque_actual->buscar_por_clave_desde(0, clave,reg_b, pos, size);
				if (encontro) {
					//saco el reg del bloque
					sacar_reg_bloque(bloque_actual,NULL, pos, size);
					if(indice_secundario!=NULL)
							indice_secundario->quitar(reg_b);					
					//pongo el nuevo registro, si no entra lo ingreso en otro lado
					if (!poner_reg_en_bloque(bloque_actual, reg)){
						//lo saco de ambos indices para despues hacer la nueva entrada
						indice_primario->quitar(clave);
						ingresar(reg);
					}
					else{
						if (indice_secundario != NULL)
							indice_secundario->ingresar(reg);
					}
					
					//si entro en el mismo bloque no es necesario tocar el indice	
				}
				else
					throw ExcepRegInexistente("Error - El registro a actualizar no existe en el archivo de datos. ");
				delete (reg_b);
				delete(bloque_actual);
			}
			catch (ExcepLeerBloque &e){
				if (bloque_actual != NULL)  
					delete (bloque_actual); 
				bloque_actual = NULL;
				delete(reg_b);
				throw e;	
			}
		}else
			throw ExcepRegInexistente("Error - El registro a actualizar no existe en el archivo de datos. ");		
	}else
		throw ExcepArchCerrado("Error - El archivo : "+ this-> get_nombre() +" esta cerrado. ");
}
		
TiposArchivo Indexado::get_tipo_archivo(){
	return INDEXADO;
}

void Indexado::eliminar() {
	(Debug::get_instance() )->mostrar_mensaje("<INDEXADO> Eliminando el archivo "+ this->get_nombre());
	//Elimino el archivo de espacios libres
	espacios_libres->eliminar();
	indice_primario->eliminar();
	if (indice_secundario!=NULL)
		indice_secundario->eliminar();
	//Elimino el archivo de header
	std::string header = "./DATA/"+get_nombre()+".head";
	remove(header.c_str());
	//Cierro el archivo
	this->cerrar();
	//Lo Elimino
	std::string mi_ruta = "./DATA/"+get_nombre();
	remove(mi_ruta.c_str()); 
}

unsigned int Indexado::get_espacio_libre() throw(ExcepArchCerrado, ExcepLeerBloque){
	unsigned int tam_libre;
	
	tam_libre =  this->espacios_libres->get_espacio_libre_datos();
		
	return tam_libre ;
}

unsigned int Indexado::get_esp_libre_indice()  throw(ExcepArchCerrado, ExcepLeerBloque){
	return indice_primario->get_espacio_libre();	
}

long Indexado::get_tam_arch_indice() throw(ExcepArchCerrado) {
	return indice_primario->get_tam();
}
 				
bool Indexado::es_unico(TipoStruct * reg){	
	//le mando el campo 0 que es el de la clave primaria
	int result = indice_primario->consultar(&(*reg)[0]);
	if( result != Indice::VACIO )
		return false;
	else		
		return true;
	
}

	
int Indexado::sacar_reg_bloque(Bloque* bloque,TipoStruct * reg, unsigned int pos_reg, unsigned int tam_reg) {
	//Saco el reg del bloque
	bloque->comprimir(pos_reg, tam_reg);
	//actualizo el arch de espacios libres.Le paso el numero de bloque y lo que libere
	espacios_libres->actualizar_espacio_libre(bloque->get_num_bloque() - 1,tam_reg);
	//lo devuelvo a disco despues de haber sacado el registro
	Buffer::get_instance()->guardar_bloque(*this,bloque);
	
	return 0;
}

bool Indexado::poner_reg_en_bloque(Bloque* bloque, TipoStruct* reg){
	//Meto el reg en el bloque
	unsigned int tam_reg = bloque->append(reg);
	if(tam_reg == 0)
		return false ;
	//actualizo el arch de espacios libres. Le paso el numero de bloque y lo que ocupe.
	espacios_libres->actualizar_espacio_libre(bloque->get_num_bloque() - 1, -tam_reg);
	//lo devuelvo a disco despues de haber puesto el reg
	Buffer::get_instance()->guardar_bloque(*this,bloque);
	return true;
}



unsigned int Indexado::get_cant_regs_indice() throw(ExcepArchCerrado){
	return this->indice_primario->get_cant_regs();
}


